#include "night.h"
#include "ui_night.h"

#include <QAbstractButton>
#include <QMessageBox>
#include <QPaintEvent>
#include <QPainter>
#include <QPushButton>
#include <QTime>
#include <QColor>

Night::Night(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::Night)
{
    ui->setupUi(this);
//    this->setGeometry(QRect(350,180,850,550));
//    setFixedSize(850,550);
    setWindowTitle("Night");
    this->setWindowIcon(QIcon(":/images/images/15.png"));

    menu1=new QMenu("New Game",this);
    menu2=new QMenu("Degree",this);
    menu3=new QMenu("Back",this);

    ui->menuBar->addMenu(menu1);
    ui->menuBar->addMenu(menu2);
    ui->menuBar->addMenu(menu3);
    connect(menu1,&QMenu::aboutToShow,this,&Night::slot_newGame);
    connect(menu3,&QMenu::aboutToShow,this,&Night::slot_exit);

    easy=new QAction("easy",this);
    normal=new QAction("normal",this);
    hard=new QAction("hard",this);
    self=new QAction("self define",this);

    menu2->addAction(easy);
    menu2->addAction(normal);
    menu2->addAction(hard);
    menu2->addAction(self);

    connect(easy,&QAction::triggered,this,&Night::slot_level_easy);
    connect(normal,&QAction::triggered,this,&Night::slot_level_normal);
    connect(hard,&QAction::triggered,this,&Night::slot_level_hard);
    connect(self,&QAction::triggered,this,&Night::slot_level_self);

    dialogSelf=new Dialog(this);
    connect(dialogSelf,&Dialog::dia_test,this,&Night::slot_level_self_get);

    flagImage= QPixmap(":/images/images/3.png");
    bombImage=QPixmap(":/images/images/2.png");

    rows = 9;
    cols = 9;
    mine = 10;
    newGame();
}

void Night::initItems()
{
    Mines.clear();
    for(int i=0;i<mine;i++)
    {
        srand(QTime::currentTime().msec());
        int x = rand()%cols;
        int y = rand()%rows;

        while(Mines.contains(QPoint(x,y)))
        {
            x = rand()%cols;
            y = rand()%rows;
        }
        Mines.append(QPoint(x,y));
    }//完成雷的初始化

    for(int i=0; i<cols; i++) //列
    {
        QVector<Item*> rowItems;//Item对象指针的容器
        for(int j=0; j<rows; j++)//行
        {
            QPoint pos = QPoint(i,j);//把新设置的位置赋给pos。
            Item* pItem = new Item(pos);//实例化了一个Item类的指针对象pItem，就算没有上一句，也可以直接用QPoint(i,j)替代pos的位置。这一步调用了Item类的有参的构造函数
            if(Mines.contains(pos))   //若该位置是雷
            {
                pItem->isMine = true;//核心操作。如果是雷，就使isMine的值为true(否则就是false,这是在items类中定义好的)
            }
            rowItems.append(pItem);//把刚才的结果赋给，或和之前声明的容器rowItems结合。循环赋了rows次。
        }
        items.append(rowItems);
    }//建立了一个二维的容器，便于鼠标事件、或者其他判断的进行

    for(int i=0;i<cols;i++)
    {
        for(int j=0;j<rows;j++)
        {
            if(items[i][j]->isMine)
            {
                continue;
            }
            int nCountMines=0;
            for(int m=-1;m<=1;m++)
            {
                for(int n=-1;n<=1;n++)
                {
                    if(m==0&&n==0)
                    {
                        continue;
                    }
                    QPoint ptNew=QPoint(i+m,j+n);
                    if(!pointInGameArea(ptNew))
                    {
                        continue;
                    }
                    if(items[i+m][j+n]->isMine)
                    {
                        nCountMines++;
                    }
                }
            }items[i][j]->number=nCountMines;
        }
    }//计算没有雷的格子周围雷的个数
}

void Night::releaseItems()
{
    for(int i=0;i<items.size();i++)
    {
        for(int j=0;j<items[i].size();j++)
        {
            if(items[i][j]!=nullptr)
            {
                delete items[i][j];//不空就删除
                items[i][j]=nullptr;
            }
        }
    }
    items.clear();
}

void Night::newGame()
{
    m_bGameFail = false;
    resize(200 + cols*30,200 + rows*30);
    releaseItems();
    initItems();
}

void Night::overGame()
{
    m_bGameFail = true;//显示雷
    for (int i=0; i<items.size(); i++)//items.size()得到的是items的第一维度的元素个数
    {
        for (int j=0;j<items[i].size(); j++)
        {
            if (items[i][j]->isMine)
            {
                items[i][j]->isMarked = true;
            }//如果是雷
            else
            {
                items[i][j]->isMarked = false;
                items[i][j]->isOpen = true;
            }//如果不是雷
        }
    }
}


void Night::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.drawPixmap(0,0,this->width(),this->height(),QPixmap(":/images/images/7.jpg"));
    painter.setPen(QPen(QColor(Qt::yellow),2));

    for(int i=0; i<cols; i++)
    {
        for(int j=0; j<rows; j++)
        {
            drawItem(painter,items[i][j]);//调用drawItem函数
        }
    }
    update();
}

void Night::drawItem(QPainter &painter, Item *pItem)
{
    if(pItem->isMarked)
    {
        if (m_bGameFail)
        {
            //游戏结束，显示雷
            QRect rcSrc(0,0,flagImage.width(),flagImage.height());
            QRect rcTarget(100 + pItem->m_pos.x()*30 + 2,100 + pItem->m_pos.y()*30 + 2,30-4,30-4);
            painter.drawPixmap(rcTarget,bombImage,rcSrc);//此时，雷已经画好
        }
        else
        {
            //游戏没结束，显示为旗子
            QRect rcSrc(0,0,flagImage.width(),flagImage.height());
            QRect rcTarget(100 + pItem->m_pos.x()*30 + 2,100 + pItem->m_pos.y()*30 + 2,30-4,30-4);
            painter.drawPixmap(rcTarget,flagImage,rcSrc);//此时，旗子已经画好
        }
        painter.setBrush(Qt::transparent);
        painter.drawRect( 100 + pItem->m_pos.x()*30,100 + pItem->m_pos.y()*30,30,30);

        return;
    }
    else if (pItem->isOpen)
    {
        if(pItem->number == 0)
            //如果为0 则为白色
        {
            painter.setBrush(Qt::transparent);//这时候白色的底色也没画好
        }
        else
        {
            //设置字体和其颜色
            QFont font;
            font.setPointSize(20);
            font.setFamily(("msyh"));
            font.setBold(true);

            painter.setBrush(Qt::transparent);
            painter.drawRect( 100 + pItem->m_pos.x()*30,
                              100 + pItem->m_pos.y()*30,30,30);

            //开始写字
            painter.setFont(font);
            painter.drawText( 100 + pItem->m_pos.x()*30  + 8,
                              100 + pItem->m_pos.y()*30 + 26,QString("%1").arg(pItem->number));//此时文字也已经写毕
            return;
        }
    }
    else
    {
        painter.setBrush(Qt::transparent);
        painter.setBrush(QColor(25,25,112));
    }
    painter.drawRect( 100 + pItem->m_pos.x()*30,100 + pItem->m_pos.y()*30,30,30);
    //这一句既填充了黄色方块，又绘制了黑线，还填充了白色底色
}

void Night::mousePressEvent(QMouseEvent *e)
{
    QPoint pt;
    pt.setX((e->pos().x()-100)/30);
    pt.setY((e->pos().y()-100)/30);
    if(!pointInGameArea(pt)){return;}
    Item* pItem = items[pt.x()][pt.y()];
    if(e->button()==Qt::LeftButton)
    {
        if(!pItem->isMarked&&!pItem->isOpen)
        {
            if(pItem->isMine)
            {
                overGame();
                QMessageBox::StandardButton ret;
                ret=QMessageBox::information(nullptr,"GAME OVER","Try Again?",
                                             QMessageBox::Yes , QMessageBox::No);
                if(ret==QMessageBox::Yes){
                    newGame();
                }
                else{
                    newGame();
                    this->close();
                }
                return;
            }else//如果不是雷，就要打开区域了
            {
                pItem->isOpen=true;
                if(pItem->number==0)
                {
                    openEmptyItem(pt);
                }
                if(findAll())
                {
                    QMessageBox::StandardButton ret;
                    ret=QMessageBox::information(nullptr,  "GAME OVER","SUCCESS! Try Again?",
                                                 QMessageBox::Yes , QMessageBox::No);
                    if(ret==QMessageBox::Yes){
                        newGame();
                    }
                    else{
                        newGame();
                        this->close();
                    }
                    return;
                }
            }
        }
    }
    else if(e->button()==Qt::RightButton)
    {
        if(pItem->isMarked)
        {
            pItem->isMarked=false;
        }
        else if(!pItem->isOpen)
        {
            pItem->isMarked=true;
            if(findAll())
            {
                QMessageBox::StandardButton ret;
                ret=QMessageBox::information(nullptr,  "GAME OVER","SUCCESS! Try Again?",
                                             QMessageBox::Yes , QMessageBox::No);
                if(ret==QMessageBox::Yes){
                    newGame();
                }
                else{
                    newGame();
                    this->close();
                }
                return;
            }
        }
    }
}

void Night::openEmptyItem(QPoint pt)
{
    //对于空白元素，有上下左右4个方向挨着的空白元素，就打开并继续查找空白元素
    QVector<QPoint> directions;
    directions.push_back(QPoint(-1,0));//左
    directions.push_back(QPoint(1,0));//右
    directions.push_back(QPoint(0,-1));//上
    directions.push_back(QPoint(0,1));//下
    //这些坐标应该都是基于pt的坐标系上的坐标
    for (int i=0; i<directions.size(); i++)
    {
        QPoint ptNew = pt + directions[i];//这个运算使得ptNew表示的就是我们要判断的坐标。这一句就是个坐标运算和赋值传递。
        if (!pointInGameArea(ptNew))
        {
            continue;
        }//不再窗口内就跳出for循环，不再判断这一个
        Item* pItem = items[ptNew.x()][ptNew.y()];
        if (!pItem->isMine && !pItem->isOpen && !pItem->isMarked && pItem->number >= 0)
        {
            //若不是雷 未被打开 未被标记 且数为0
            //则将其打开
            pItem->isOpen = true;

            //对于找到的空白元素，在它的8个方向上有数字元素就打开
            QVector<QPoint> directions2 = directions;
            directions2.push_back(QPoint(-1,-1));
            directions2.push_back(QPoint(1,1));
            directions2.push_back(QPoint(1,-1));
            directions2.push_back(QPoint(-1,1));
            for (int j=0; j<directions2.size(); j++)
            {
                //对四个方向进行递归查看
                QPoint ptNew2 = ptNew + directions2[j];
                if(!pointInGameArea(ptNew2))
                {
                    continue;
                }//若不在窗口内就跳出小的这个for循环，不再判断这一个
                Item* pItem2 = items[ptNew2.x()][ptNew2.y()];
                if (!pItem2->isMine && !pItem2->isOpen && !pItem2->isMarked && pItem2->number > 0)
                {
                    //若不是雷 未被打开 未被标记 且数大于0
                    //则将其打开
                    pItem2->isOpen = true;
                }
            }
            //递归查找上下左右4个方向的空白元素
            openEmptyItem(ptNew);
        }
    }
}


bool Night::findAll()
{
    bool bFindAll = true;//新声明了一个bool型数据
    for (int i=0; i<items.size(); i++)
    {
        for (int j=0;j<items[i].size(); j++)
        {
            //只要存在一个雷没被标记，或存在一个非雷被没打开，都不算找完
            Item* pItem = items[i][j];
            if (pItem->isMine)
            {
                if (!pItem->isMarked)
                {
                    bFindAll = false;
                }
            }
            else
            {
                if (!pItem->isOpen)
                {
                    bFindAll = false;
                }
            }
        }
    }
    return bFindAll;
}

bool Night::pointInGameArea(QPoint pt)
{
    if(pt.x()>=0 && pt.x()< cols && pt.y()>=0 && pt.y()< rows)
    {
        return true;
    }
    return false;
}//判断点是否在游戏窗口内

void Night::slot_level_easy()
{
    rows = 9;
    cols = 9;
    mine = 10;
    newGame();
}

void Night::slot_level_normal()
{
    rows = 16;
    cols = 16;
    mine = 40;
    newGame();
}

void Night::slot_level_hard()
{
    rows = 16;
    cols = 30;
    mine = 99;
    newGame();
}

void Night::slot_level_self()
{
    dialogSelf->show();
}

void Night::slot_level_self_get()
{
    rows=dialogSelf->getRows();
    cols=dialogSelf->getCols();
    mine=dialogSelf->getMine();
    newGame();
}

void Night::slot_exit()
{
    newGame();
    this->close();
}

void Night::slot_newGame()
{
    newGame();
}

Night::~Night()
{
    delete ui;
}
